#include <cserver/restapi.h>
#include <cserver/context.h>
#include <fcgiapp.h>
#include <fcgio.h>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <vector>
#include <chrono>

#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/uuid/sha1.hpp>
#include <cserver/uri.h>
#include <cserver/module.h>

#include <xirang2/assert.h>

#include <unistd.h>	// for close

namespace csrv{
	namespace{
		const char* K_hex_table= "0123456789abcdef";
		const std::string K_null = "null";
	}
	std::string escape_null(const std::string& s){
		return s.empty()? K_null : s;
	}
	std::string escape_buffer(const std::vector<char>& buf){
		std::string sb;
		sb.push_back('"');

		for(auto b : buf){
			uint8_t ch = static_cast<unsigned char>(b);
			if (ch < 0x20){
				sb += "\\u00";
				sb.push_back(K_hex_table[(ch >> 4)]);
				sb.push_back(K_hex_table[(ch & 0x0f)]);
			}
			else {
				if(ch == '\\' || ch == '"'){
					sb.push_back('\\');
				}
				sb.push_back(char(ch));
			}
		}
		sb.push_back('"');
		return sb;
	}

	std::string escape_char(char b){
		uint8_t ch = b;
		std::string sb;
		sb.push_back('"');
		if (ch < 0x20){
			sb += "\\u00";
			sb.push_back(K_hex_table[(ch >> 4)]);
			sb.push_back(K_hex_table[(ch & 0x0f)]);
		}
		else {
			if(ch == '\\' || ch == '"'){
				sb.push_back('\\');
			}
			sb.push_back(char(ch));
		}
		sb.push_back('"');
		return sb;
	}

	std::string escape_string(const std::string& str){
		std::string sb;
		sb.push_back('"');

		for(auto c : str){
			unsigned int ch = static_cast<unsigned char>(c);
			if (ch < 0x20){
				sb += "\\u00";
				sb.push_back(K_hex_table[(ch >> 4)]);
				sb.push_back(K_hex_table[(ch & 0x0f)]);
			}
			else {
				if(ch == '\\' || ch == '"'){
					sb.push_back('\\');
				}
				sb.push_back(char(ch));
			}
		}
		sb.push_back('"');
		return sb;

	}
	std::string result2json(const pqxx::result& result, size_t strColidx){
		std::stringstream fout;
		fout << "[";
		bool first = true;
		for(auto row : result){
			if (first)
				first = false;
			else
				fout << ",";

			fout << "{";

			size_t colnum = 0;
			for (auto field : row){
				if (colnum != 0)
					fout << ",";

				fout << escape_string(field.name()) << ":";
				if (colnum < strColidx)
					fout << escape_string(field.c_str());
				else
					fout << escape_null(field.c_str());

				++colnum;
			};
			fout << "}";
		}
		fout << "]";

		return fout.str();
	}

	std::string hash_password(const std::string& account, const std::string& password){
		auto text = password + "{" + account + "}";
		boost::uuids::detail::sha1 sha1;
		sha1.process_bytes(text.c_str(), text.size());

		unsigned int digest[5];
		sha1.get_digest(digest);
		for(auto& v : digest){
			v = (v>>16) | (v<<16);
			v = ((v >> 8) &0xff00ff) | ((v<<8)& 0xff00ff00);
		}

		std::string hashed_text;
		hashed_text.reserve(40);

		const unsigned char* itr = reinterpret_cast<unsigned char*>(digest);
		auto end = itr + sizeof(digest);
		for (; itr != end; ++itr){
			hashed_text.push_back(K_hex_table[((*itr) >> 4) & 0xF]);
			hashed_text.push_back(K_hex_table[(*itr) & 0xF]);
		}
		return hashed_text;
	}

	std::map<std::string, std::string> parseQuery(const std::string& query){
		std::map<std::string, std::string> result;

		try {
			auto end = query.end();
			auto i_key = query.begin();

			for(;i_key != end;){
				auto i_end = std::find(i_key, end, '&');
				auto i_value = std::find(i_key, i_end, '=');
				auto key = boost::trim_copy(std::string(i_key, i_value));
				if (i_value != i_end) ++i_value;
				auto value = boost::trim_copy(std::string(i_value, i_end));
				if (!key.empty())
					result[key] = http_decode_string(value);
				i_key = i_end;
				if (i_key != end) ++i_key;
			}
		}
		catch(xirang2::exception& e){
			BOOST_LOG_TRIVIAL(info) << e.what() << ": " << query;
		}
		catch(std::exception& e){
			BOOST_LOG_TRIVIAL(info) << e.what()<< ": " << query;
		}
		return result;

	}
	std::map<std::string, std::string> parseCookie(const std::string& cookie){
		std::map<std::string, std::string> result;

		auto end = cookie.end();
		auto i_key = cookie.begin();

		for(;i_key != end;){
			auto i_end = std::find(i_key, end, ';');
			auto i_value = std::find(i_key, i_end, '=');
			auto key = boost::trim_copy(std::string(i_key, i_value));
			if (i_value != i_end) ++i_value;
			auto value = boost::trim_copy(std::string(i_value, i_end));
			if (!key.empty())
				result[key] = value;
			i_key = i_end;
			if (i_key != end) ++i_key;
		}
		return result;
	}
	std::string makeCookie(const std::map<std::string, std::string>& cookie){
		std::stringstream result;
		bool first = true;
		for(auto& v : cookie){
			if (first)
				first = false;
			else
				result << "; ";
			result << v.first << "=" << v.second;
		}

		return result.str();
	}
	void response_header(std::ostream& os, int code, bool endhead){
		os << "Status: " << code << "\r\n";
		os << "Content-type: text/plain\r\n";
		if (endhead)
			os << "\r\n";

	}

	void response_error(std::ostream& os, int code, const std::string& cat, const std::string& reason, const std::string& callback){
		response_header(os, code);
		if (!callback.empty()) os << callback << "(\"";

		os <<"{\"category\":\"" << cat << "\",\"reason\":\"" << reason << "\"}";

		if (!callback.empty()) os << "\")";
	}
	void response_error(std::ostream& os, int code, const std::string& cat, const std::string& reason, Request& req){
		auto query = parseQuery(req.getParam("QUERY_STRING"));
		auto& callback = query["callback"];
		response_error(os, code, cat, reason, callback);
	}
	extern std::string totalCount(pqxx::connection& conn, const std::string& sql){
		pqxx::work w(conn);
		auto result = w.prepared(sql).exec();
		return escape_null(result.begin()->begin()->c_str());
	}
	TotalCount::TotalCount(pqxx::work& w_, const std::string& sql)
		: w(w_)
		  , invo(w.prepared(sql))
	{ }
	std::string TotalCount::result(){
		return escape_null(invo.exec().begin()->begin()->c_str());
	}


	CommandAddHelper::CommandAddHelper(std::string m, std::string c, CmdHandler f, std::set<std::string> roles){
		auto call = [f](RequestContext& ctx){
			Request& req = ctx.req;
			pqxx::connection& dbconn = ctx.conn;
			SessionContext& sctx = ctx.sctx;
			const std::string& command = ctx.command;
			f(req, dbconn, sctx, command);
		};
		GetRestApiTable()[m][c] = RestApiHanlderDef{call, roles};
	}
	struct WebWorker;

	struct RestApi::RestApiImp{
		RestApiImp(Context& ctx) 
			: context(ctx)
			  , sessions(mutex)
		{
			running = false;
			load_user_info();
			dump_debug();
		}
		void load_user_info(){
			pqxx::connection dbconn(context.config.db_string.c_str());
			dbconn.prepare("get_users","select account, id, name, password, role from t_user2");
			dbconn.prepare("get_sessions","select account, guid from t_session, t_user2  where last_access_time > current_date - 7 and t_user2.id = t_session.userid");
			dbconn.prepare("get_permit","select role, app from t_user_modules");
			{
				pqxx::work w(dbconn);
				auto result = w.prepared("get_users").exec();
				for(auto row : result){
					auto itr = row.begin();
					std::string account = itr->c_str();
					UserInfo uinfo;
					uinfo.userid = (++itr)->c_str();
					uinfo.displayName = (++itr)->c_str();
					uinfo.password = (++itr)->c_str();
					uinfo.role = (++itr)->c_str();
					sessions.user_info[account] = uinfo;
				}
			}
			{
				pqxx::work w(dbconn);
				auto result = w.prepared("get_sessions").exec();
				for(auto row : result){
					auto itr = row.begin();
					std::string account = itr->c_str();
					std::string sessionid = (++itr)->c_str();
					sessions.activeSessions[sessionid] = account;
				}
			}
			{
				pqxx::work w(dbconn);
				auto result = w.prepared("get_permit").exec();
				for(auto row : result){
					auto itr = row.begin();
					std::string account = itr->c_str();
					std::string app = (++itr)->c_str();
					sessions.role_permission[account].insert(app);
				}
			}
		}
		void dump_debug(){
			for(auto& v : GetRestApiTable()){
				std::cerr << v.first << std::endl;
				for(auto& c : v.second){
					std::cerr << "\t|" << c.first << "|" << std::endl;
				}
			}

		}

		Context& context;

		int sock_id = -1;
		std::mutex mutex, accept_mutex;

		std::atomic<bool> running;

		std::vector<std::thread> thread_pool;

		SessionContext sessions;
	};

	struct WebWorker{
		RestApi::RestApiImp& master;
		pqxx::connection dbconn;

		WebWorker(RestApi::RestApiImp& imp) 
			: master(imp)
			  , dbconn(imp.context.config.db_string.c_str())
		{
			dbconn.prepare("write_test", "INSERT INTO test(text) values($1)");

			dbconn.prepare("delete_sessionid", "delete from t_session where guid =$1");
			dbconn.prepare("new_sessionid", "select uuid_generate_v4()");
			dbconn.prepare("store_sessionid", "insert into t_session (userid, guid) values($1, $2)");
			dbconn.prepare("moduser", "update t_user2 set name=$1, password=$2, role=$3 where id=$4"); // for password, or others
			dbconn.prepare("delsession", "delete from t_session where userid = $1");
			dbconn.prepare("deluser", "delete from t_user2 where id=$1");
			dbconn.prepare("adduser", "insert into t_user2(account, name, password, role, orgid) values($1, $2, $3, $4, 1)");
			dbconn.prepare("get_userid", "select id from t_user2 where account = $1");

			dbconn.prepare("get_hydrant", "select euid, time, address, id, pressure, longitude, latitude, elevation from v_hydrant_status where euid like $1 or address like $1 order by euid limit $2 offset $3");
			dbconn.prepare("get_hydrant_total", "select count(*) from v_hydrant_status where euid like $1 or address like $1");
			dbconn.prepare("get_hydrant_issue", "select euid, time, address, id, pressure, longitude, latitude, elevation, (case"
					" when pressure between  10000 and 49999 then 3 "
					" when pressure < 10000 then 4 "
					" when pressure is null then 5 end"
					" ) as status from v_hydrant_status where (pressure < 50000 or pressure is null) and (euid like $1 or address like $1) order by euid limit $2 offset $3");
			dbconn.prepare("get_hydrant_issue_total", "select count(*) from v_hydrant_status where (pressure < 50000 or pressure is null) and (euid like $1 or address like $1)");

			dbconn.prepare("get_hydrant_data", "select c.date, c.min_pressure, c.max_pressure, d.pressure as cur_pressure from "
					" (select b.euid, max(b.time) tm, date(b.time) date, min(b.pressure) min_pressure, max(b.pressure) max_pressure  "
					" from t_node_info2 a, t_hydrant_data b "
					" where a.id = b.euid and a.euid = $1 and date(b.time) >= $2 and date(b.time) <= $3 group by b.euid, date(b.time)) c, t_hydrant_data d "
					" where c.euid = d.euid and c.tm = d.time"
					" order by c.date limit 2000");

			dbconn.prepare("get_hydrant_data_total", "select count(*) from (select count(date(b.time)) from t_node_info2 a, t_hydrant_data b "
					" where a.id = b.euid and a.euid = $1 and date(b.time) >= $2 and date(b.time) <= $3 group by date(b.time)) c ");

			dbconn.prepare("get_hydrant_data_history", "select a.euid, b.time,  a.sim_id, c.address, b.pressure from t_node_info2 a, t_hydrant_data b, t_hydrant c "
					" where a.id = b.euid and a.id = c.euid and (a.euid like $1 or a.sim_id like $1 or c.address like $1 ) limit $2 offset $3");
			dbconn.prepare("get_hydrant_data_history_total", "select count(*) from (select * from t_node_info2 a, t_hydrant_data b, t_hydrant c"
					" where a.id = b.euid and a.id = c.euid and (a.euid like $1 or a.sim_id like $1 or c.address like $1 ) limit 2000) d");

			dbconn.prepare("get_hydrant_data_history_by_date", "select a.euid, b.time,  a.sim_id, c.address, b.pressure from t_node_info2 a, t_hydrant_data b, t_hydrant c "
					" where a.id = b.euid and a.id = c.euid and a.euid=$1 and date(b.time)=$2 order by b.time desc limit $3 offset $4");
			dbconn.prepare("get_hydrant_data_history_by_date_total", "select count(*) from t_node_info2 a, t_hydrant_data b, t_hydrant c "
					" where a.id = b.euid and a.id = c.euid and a.euid=$1 and date(b.time)=$2");

			dbconn.prepare("get_hydrant_by_area", "select euid, time, address, id, pressure, longitude, latitude, elevation from v_hydrant_status "
					" where longitude between $1 and $2 and latitude between $3 and $4 order by euid limit $5 offset $6");
			dbconn.prepare("get_hydrant_by_area_total", "select count(*) from v_hydrant_status "
					" where latitude between $1 and $2 and latitude between $3 and $4 ");
			dbconn.prepare("get_hydrant_by_area2", "select euid, time, address, id, pressure, longitude, latitude, elevation from v_hydrant_status "
					" limit $1 offset $2");
			dbconn.prepare("get_hydrant_by_area_total2", "select count(*) from v_hydrant_status");
			dbconn.prepare("get_hydrant_gps_rect", "select min(longitude) west, max(longitude) east, min(latitude) south, max(latitude) north from v_hydrant_status");

			dbconn.prepare("hydrant_status_summary", "select status, count(*) from (select (case when pressure>=100000 then 1"
					" when pressure between 50000 and 99999 then 2"
					" when pressure between  10000 and 49999 then 3 "
					" when pressure < 10000 then 4"
					" else 5  end"
					" ) as status from v_hydrant_status) as a group by status");

			dbconn.prepare("energy_sum_by_month", "select extract(year from time) as year, extract (month from time) as month, sum(energy) as energy"
					" from t_node_state where date(time) between $1 and $2 group by year, month");
			dbconn.prepare("energy_sum_by_month_total", "select count(*) from (select extract(year from time) as year, extract (month from time) as month "
					" from t_node_state where date(time) between $1 and $2 group by year, month) as a ");

			dbconn.prepare("energy_sum_by_date", "select date(time) as date, sum(energy) as energy "
					" from t_node_state where date(time) between $1 and $2 group by date");
			dbconn.prepare("energy_sum_by_date_total", "select count(*) from ( select date(time) as date "
					" from t_node_state where date(time) between $1 and $2 group by date ) as a");

			dbconn.prepare("status_summary", "select t_pole.status, count(*) as count from t_section, t_pole where t_pole.section_id = t_section.id group by t_pole.status");

			dbconn.prepare("list_pole_by_area", "select serial, euid, section_id, id, gps_x as longitude, gps_y as latitude "
					" from t_pole where gps_x between $1 and $2 gps_y between $3 and $4 order by euid limit $5 offset $6");
			dbconn.prepare("list_pole_by_area_total", "select count(*) from (select euid "
					" from t_pole where gps_x between $1 and $2 gps_y between $3 and $4) as a");

			dbconn.prepare("list_pole_by_area_section", "select serial, euid, section_id, id, gps_x as longitude, gps_y as latitude "
					" from t_pole where gps_x between $1 and $2 gps_y between $3 and $4 and section_id=$5 order by euid limit $6 offset $7");
			dbconn.prepare("list_pole_by_area_section_total", "select count(*) "
					" from t_pole where gps_x between $1 and $2 gps_y between $3 and $4 and section_id=$5 ");

			dbconn.prepare("list_issue_pole", "select a.name,a.owner,a.contactor, b.serial, b.euid, b.issue_time, b.section_id,b.gps_x as longitude, b.gps_y as latitude,"
					" b.status, b.id from t_section as a, t_pole as b where b.section_id = a.id and b.status > 1 limit $1 offset $2");
			dbconn.prepare("list_issue_pole_total", "select count(*) "
					" from t_section as a, t_pole as b where b.section_id = a.id and b.status > 1");

			dbconn.prepare("list_issue_pole_by_section", "select a.name,a.owner,a.contactor, b.serial, b.euid, b.issue_time, b.section_id,b.gps_x as longitude, b.gps_y as latitude,"
					" b.status, b.id from t_section as a, t_pole as b where b.section_id = a.id and b.status > 1 and section_id=$1");
			dbconn.prepare("list_issue_pole_by_section_total", "select count(*) "
					" from t_section as a, t_pole as b where b.section_id = a.id and b.status > 1 and section_id=$1 limit $2 offset $3");

			dbconn.prepare("get_light_energy", "select euid, time, last_data_time, last_error_time, id, is_on, status, energy from t_node_state "
					" where euid = $1 and time between $2 and $3 order by euid limit 2000");
			dbconn.prepare("get_light_energy_total", "select count(*) from (select euid from t_node_state "
					" where euid = $1 and time between $2 and $3 limit 2000) as a");

			dbconn.prepare("list_section", "select name, owner, contactor, contactor_info, id, parent, report_intervel, center_longitude, center_latitude"
					" from t_section order by id limit $1 offset $2");
			dbconn.prepare("list_section_total", "select count(*) from t_section");

			dbconn.prepare("get_section", "select name, owner, contactor, contactor_info, id, parent, report_intervel, center_longitude, center_latitude"
					" from t_section where id=$1");

			dbconn.prepare("list_pole", "select serial, euid, section_id, id, gps_x as longitude, gps_y as latitude "
					" from t_pole order by id limit $1 offset $2");
			dbconn.prepare("list_pole_total", "select count(*) from t_pole ");
			
			dbconn.prepare("get_pole", "select serial, euid, section_id, id, gps_x as longitude, gps_y as latitude "
					" from t_pole where id = $1");

			dbconn.prepare("list_pole_by_section", "select serial, euid, section_id, id, gps_x as longitude, gps_y as latitude "
					" from t_pole where section_id=$1 order by id limit $2 offset $3");
			dbconn.prepare("list_pole_by_section_total", "select count(*)"
					" from t_pole where section_id=$1 ");
			dbconn.prepare("get_session", "select userid from t_session where guid=$1 ");
			dbconn.prepare("get_info_by_userid", "select a.id, a.account, a.name, a.orgid, b.name orgname from t_user2 a, t_organization b "
					" where a.orgid = b.id and a.id=$1");

			for(auto& p :  GetApiSqlTable()){
				dbconn.prepare(p.second.sql_name, p.second.sql);
				//std::cout << "SQL:" << p.second.sql_name << ": " << p.second.sql<< std::endl;
				if (!p.second.sql_total_name.empty()){
					dbconn.prepare(p.second.sql_total_name, p.second.totalSql);
					//std::cout << "SQL:" << p.second.sql_total_name << ": " << p.second.totalSql<< std::endl;
				}
			}
		}

		SessionInfo loadSessionInfo(pqxx::connection& dbconn, const std::string& userid){
			SessionInfo sinfo;
			pqxx::work w(dbconn);
			{
				auto result = w.prepared("get_info_by_userid")(userid).exec();
				auto row = result.begin();
				auto field = row->begin();

				sinfo.userid = field->c_str(); ++field;
				sinfo.account = field->c_str(); ++field;
				sinfo.displayName = field->c_str(); ++field;
				sinfo.orgid = field->c_str(); ++field;
				sinfo.orgname = field->c_str();
			}
			// load roles
			{
				auto records = w.prepared("v2_sql_get_modules_by_userid")(userid).exec();
				for(auto r : records){
					auto field = r.begin();
					std::string role = field->c_str(); 
					++field;
					sinfo.module_role[field->c_str()] = role;
				}
			}
			return sinfo;
		}
		bool permissionCheck(const std::string& method, const std::string& cmd, Request& req, pqxx::connection& dbconn, const std::set<std::string>& permitRoles){
			if (cmd == "api/v1/common/login" || cmd == "api/v2/account/login")
				return true;

			StreamGroup sg(req);
			auto sessionid = req.getCookie("sessionid");
			std::lock_guard<std::mutex> guard(master.mutex);

			if ( boost::starts_with(cmd, "api/v1/")){
				auto pos = master.sessions.activeSessions.find(sessionid);
				if( pos == master.sessions.activeSessions.end()){
					BOOST_LOG_TRIVIAL(info) << " Permission Failed, sessionid not found " << sessionid;
					response_error(sg.fout, 403, "ErrSession", "NoSessionID", req);
					return false;
				}
				auto& uinfo = master.sessions.user_info.find(pos->second)->second;
				bool hasPermission = permitRoles.count(uinfo.role) != 0;
				if (!hasPermission)
					response_error(sg.fout, 403, "ErrPermission", "PermissionDenied", req);

				return hasPermission;
			}
			else if (boost::starts_with(cmd, "api/v2/")){	// api/v2/
				if (boost::starts_with(cmd, "api/v2/pub/")) return true;
				auto pos = master.sessions.sessionTable.find(sessionid);
				if (pos == master.sessions.sessionTable.end()){
					std::string userid;
					{
						pqxx::work w(dbconn);
						auto result = w.prepared("get_session")(sessionid).exec();
						if (!result.empty())
							userid = result.begin()->begin()->c_str();
					}
					if (userid.empty()){
						BOOST_LOG_TRIVIAL(info) << " Permission Failed, sessionid not found " << sessionid;
						response_error(sg.fout, 403, "ErrSession", "NoSessionID", req);
						return false;
					}
					else {
						master.sessions.sessionTable[sessionid] = loadSessionInfo(dbconn, userid);
						pos = master.sessions.sessionTable.find(sessionid);
					}
				}

				auto idx = cmd.find_last_of('/');
				XR_PRE_CONDITION(idx != std::string::npos);
				std::string module(cmd.begin(), cmd.begin() + idx);

				auto module_pos = pos->second.module_role.find(module);
				bool hasPermission = module_pos != pos->second.module_role.end();
				hasPermission = hasPermission && (permitRoles.count(module_pos->second) != 0);
				if (!hasPermission)
					response_error(sg.fout, 403, "ErrPermission", "PermissionDenied", req);
				return hasPermission;
			}
			else 
				return (boost::starts_with(cmd, "pub/"));
		}

		void run_worker(){
			{
				std::stringstream sstr;
				sstr << "Web thread, id: " << std::this_thread::get_id();
				pqxx::work w(dbconn);
				w.prepared("write_test")(sstr.str()).exec();
				w.commit();
			}
			for(;;) try{
				Request request(master.sock_id);
				request.context = &master.context;
				{
					std::lock_guard<std::mutex> guard(master.accept_mutex);
					if (!request.accept())
						break;
				}
				auto cmd = request.getParam("PATH_INFO");
				auto method = request.getParam("REQUEST_METHOD");
				BOOST_LOG_TRIVIAL(info) << "Web request on " << std::this_thread::get_id() << ", " << method << " " << cmd;
				auto start = std::chrono::steady_clock::now();

				auto pos = GetRestApiTable().find(method);
				decltype(pos->second.find(cmd)) pos_cmd;
				if (pos != GetRestApiTable().end())
					pos_cmd = pos->second.find(cmd);

				if (pos == GetRestApiTable().end() || pos_cmd == pos->second.end())
				{
					StreamGroup sg(request);
					response_error(sg.fout, 404, "ErrNotFound", "APINotFound " + method + " " +cmd, request);
				}
				else try{
					if (permissionCheck(method, cmd, request, dbconn, pos_cmd->second.permitRoles)){
						RequestContext rctx{std::ref(request), std::ref(dbconn), std::ref(master.sessions), cmd, method};
						pos_cmd->second.call(rctx);
					}
				}
				catch(std::exception& e){
					BOOST_LOG_TRIVIAL(info) << "Exception: " << e.what() ;
					StreamGroup sg(request);
					response_error(sg.fout, 400, "ErrRuntime", "BadParameter", request);
				}
				catch(...){
					StreamGroup sg(request);
					response_error(sg.fout, 400, "ErrRuntime", "UnknownError", request);
				}
				std::chrono::duration<double> fp_ms(std::chrono::steady_clock::now() - start);
				BOOST_LOG_TRIVIAL(info) << " 	Time " << fp_ms.count() << "s";
			}
			catch(std::exception& e){
				BOOST_LOG_TRIVIAL(info) << "Unexpected Exception: " << e.what() ;
			}
			catch(...){
				BOOST_LOG_TRIVIAL(info) << "Unknown Exception";
			}
		}
	};


	RestApi::RestApi(Context& ctx) : m_imp(new RestApi::RestApiImp(ctx)){
		FCGX_Init();
	}
	RestApi::~RestApi() = default;

	void RestApi::start(){
		if (m_imp->running) return;

		m_imp->sock_id = FCGX_OpenSocket(m_imp->context.config.sock_path.c_str(), 100);
		if (m_imp->sock_id < 0)
			throw std::runtime_error("Failed to open sock file " + m_imp->context.config.sock_path);

		m_imp->running = true;
		auto max_threads = m_imp->context.config.max_rest_threads;

		if (max_threads == -1){
			max_threads = std::thread::hardware_concurrency();
		}

		{
			std::lock_guard<std::mutex> guard(m_imp->mutex);
			while (max_threads--){
				auto worker = std::make_shared<WebWorker>(*m_imp);
				//workers.emplace_back(worker);
				m_imp->thread_pool.emplace_back([worker]{worker->run_worker();});
			}

		}
	}

	void RestApi::stop(){
		m_imp->running = false;

		close(m_imp->sock_id);

		for(auto& t : m_imp->thread_pool)
			t.join();
	}

}

